#include <SDL.h>
#include <sys/stat.h>
#include "string.h"
#include "i_loader.h"
#include "dirlist.h"
#include "options.h"

/* Create directory for save data. */
int							MakeSaveDir			(char* aBase)
{
	int ret = mkdir(SAVE_PATH, 0777);
	char fullpath[MAX_PATH];

	if(ret && errno != EEXIST)
	{
		return -1;
	}

	snprintf(fullpath, MAX_PATH, SAVE_PATH"/%s", aBase);

	ret = mkdir(fullpath, 0777);

	return (ret && errno != EEXIST) ? -1 : 0;
}

/* Argument list */
static char*				Arguments[64];
static uint32_t				ArgumentCount;

void						AttachString		(const char* aArgument)
{
	if(ArgumentCount < 63)
	{
		Arguments[ArgumentCount ++] = strdup(aArgument);
	}
}

void						AttachInt			(int32_t aArgument)
{
	if(ArgumentCount < 63)
	{
		Arguments[ArgumentCount] = malloc(32);
		snprintf(Arguments[ArgumentCount++], 32, "%d", aArgument);
	}
}

/* Option list */
typedef struct
{
	const char*				Value;
	const char*				Name;
}	EnumEntry;

enum						OptionType
{
	otFINAL, otINT, otBOOL, otSTRING, otENUM, otFILE
};

typedef struct
{
	const char*				ArgumentName;
	const char*				Description;
	enum OptionType			Type;
	int32_t					IntMin;
	int32_t					IntMax;
	int32_t					IntValue;
	const EnumEntry*		EnumValues;
	const struct PWAD*		Files;
	uint32_t*				FileCount;
	char					StringValue[1024];
	uint32_t				Active;
}	OptionDef;

static const EnumEntry		DifficultyNames[] = 
{
	{"1", "I'm too young to die."},
	{"2", "Hey, not too rough."},
	{"3", "Hurt me plenty."},
	{"4", "Ultra-Violence."},
	{"5", "Nightmare!"}
};

static const EnumEntry		EXENames[] = 
{
	{"chex",	"Chex Quest"}
};

/*static const EnumEntry		CompatibilityModes[] = 
{
	{"0",	"DOOM 1.2"},
	{"1",	"DOOM 1.666"},
	{"2",	"DOOM2 1.9"},
	{"3",	"Ultimate DOOM"},
	{"4",	"Final DOOM"},
	{"5",	"DOS DOOM"},
	{"6",	"TAS DOOM (Unused)"},
	{"7",	"BOOM"},
	{"8",	"BOOM 2.01"},
	{"9",	"BOOM 2.02"},
	{"10",	"lxDOOM 1.4.x"},
	{"11",	"MBF"},
	{"12",	"prBOOM 1"},
	{"13",	"prBOOM 2"},
	{"14",	"prBOOM 3"},
	{"15",	"prBOOM 4"},
	{"16",	"prBOOM 5"},
	{"17",	"prBOOM 6"},
};*/

static OptionDef				Options[] = 
{
	{	"-file", "Specifies a list of PWAD files to load.",					otFILE,	0, 0, 0, 0, PWADS, &FoundPWADS	},
	{	"-deh", "Tells PrBoom to load the dehacked patch.",					otFILE,	0, 0, 1, 0, DEH, &FoundDEH		},
	{	"-exe", "Behave like 'Chex Quest', need the WAD and DEH file too.",	otENUM,	0, 0, 0, EXENames				},
	{	"-warp", "Level to start game on.",									otINT,	1, 50, 1						},
	{	"-skill", "Skill level to begin the game at",						otENUM,	0, 4, 2, DifficultyNames		},
	{	"-respawn", "Monsters respawn after a short period.",				otBOOL									},
	{	"-fast", "Monsters move faster.",									otBOOL									},
	{	"-nomonsters", "Remove all monsters from the game.",				otBOOL									},
	{	"-solo-net", "Start a 'single player coop' game",					otBOOL									},
	{	0																											}
/*	{	"-net", "IP:PORT of net server.",									otMINT									}, */
/*	{	"-complevel", "Set the Demo compatibility mode",					otENUM,	0, 17, 0, CompatibilityModes	}, */
};

/* String generators for lists */
static const char*	OPTIONS_GetString			(uint32_t aIndex)
{
	static char namebuffer[1024];

	namebuffer[0] = 0;

	if(Options[aIndex].Type)
	{
		if(Options[aIndex].Type == 1)
		{
			snprintf(namebuffer, 1024, "[%s] %20s: %d", Options[aIndex].Active ? "X" : " ", Options[aIndex].ArgumentName, Options[aIndex].IntValue);
		}
		else if(Options[aIndex].Type == 2)
		{
			snprintf(namebuffer, 1024, "[%s] %20s", Options[aIndex].Active ? "X" : " ", Options[aIndex].ArgumentName);
		}
		else if(Options[aIndex].Type == 3 || Options[aIndex].Type == 5)
		{
			snprintf(namebuffer, 1024, "[%s] %20s: %s", Options[aIndex].Active ? "X" : " ", Options[aIndex].ArgumentName, Options[aIndex].StringValue);
		}
		else if(Options[aIndex].Type == 4)
		{
			snprintf(namebuffer, 1024, "[%s] %20s: %s", Options[aIndex].Active ? "X" : " ", Options[aIndex].ArgumentName, Options[aIndex].EnumValues[Options[aIndex].IntValue].Name);
		}

		return namebuffer;
	}

	return 0;
}

static struct PWAD*	DrawFiles;
static uint32_t		DrawFileCount;
static const char*	OPTIONS_GetFileString		(uint32_t aIndex)
{
	if(aIndex < DrawFileCount)
	{
		return DrawFiles[aIndex].BaseName;
	}

	return 0;
}

/* List render utility */
typedef const char*	(*OPTIONS_Draw_GetString)	(uint32_t aIndex);
static void			OPTIONS_Draw				(const char* aDescription, OPTIONS_Draw_GetString aCallback, uint32_t aSelected)
{
	SDL_Surface* text;
	int32_t index = 0;
	SDL_Color c_active = {0xFF, 0xFF, 0xFF, 0xFF}, c_inactive = {0x50, 0x50, 0x50, 0xFF}, bg = {0, 32, 64, 0};
	SDL_Rect DrawPosition = {10, 50, 0, 0};

	/* Clear the screen */
	SDL_FillRect(SDL_GetVideoSurface(), 0, SDL_MapRGB(SDL_GetVideoSurface()->format, 0, 32, 64));

	/* Draw the description */
	if(aDescription)
	{
		SDL_Rect descposition = {10, 10, 0, 0};
		text = STRING_Render(aDescription, c_active, bg);
		SDL_BlitSurface(text, 0, SDL_GetVideoSurface(), &descposition);
		SDL_FreeSurface(text);
	}

	/* Draw the strings */
	while(1)
	{
		const char* string = aCallback(index);

		if(string)
		{
			text = STRING_Render(string, (index == aSelected) ? c_active : c_inactive, bg);
			SDL_BlitSurface(text, 0, SDL_GetVideoSurface(), &DrawPosition);
			DrawPosition.y += text->h;
			SDL_FreeSurface(text);
		}
		else
		{
			break;
		}

		index ++;
	}

	SDL_Flip(SDL_GetVideoSurface());
}

/* Input functions */
static int			OPTIONS_NULLInput			(OptionDef* aOption, SDLKey aKey)
{
	return 0;
}

static int			OPTIONS_IntInput			(OptionDef* aOption, SDLKey aKey)
{
	if(aOption && (aKey == SDLK_LEFT || aKey == SDLK_RIGHT))
	{
		aOption->IntValue += (aKey == SDLK_RIGHT) ? 1 : -1;
		aOption->IntValue = (aOption->IntValue < aOption->IntMin) ? aOption->IntMin : aOption->IntValue;
		aOption->IntValue = (aOption->IntValue > aOption->IntMax) ? aOption->IntMax : aOption->IntValue;
		return 1;
	}
	return 0;
}

static int			OPTIONS_StringInput			(OptionDef* aOption, SDLKey aKey)
{
	return 0;
}

static int			OPTIONS_FileListInput		(OptionDef* aOption, SDLKey aKey)
{
	if(aOption && (aKey == SDLK_LEFT))
	{
		aOption->StringValue[0] = 0;
		return 1;
	}

	if(aOption && (aKey == SDLK_RIGHT))
	{
		SDL_Event e;
		int32_t SelectedFile = 0;

		DrawFiles = aOption->Files;
		DrawFileCount = *aOption->FileCount;

		OPTIONS_Draw(aOption->Description, OPTIONS_GetFileString, SelectedFile);

		while(*(aOption->FileCount) && SDL_WaitEvent(&e))
		{
			if(e.type == SDL_KEYDOWN)
			{
				if(e.key.keysym.sym == SDLK_DOWN && SelectedFile < (*(aOption->FileCount) - 1))
				{
					SelectedFile ++;
					OPTIONS_Draw(aOption->Description, OPTIONS_GetFileString, SelectedFile);
				}
				else if(e.key.keysym.sym == SDLK_UP && SelectedFile)
				{
					SelectedFile --;
					OPTIONS_Draw(aOption->Description, OPTIONS_GetFileString, SelectedFile);
				}
				else if(e.key.keysym.sym == SDLK_RETURN)
				{
					if(!aOption->IntValue)
					{
						strncat(aOption->StringValue, " ", 1024);
						strncat(aOption->StringValue, aOption->Files[SelectedFile].BaseName, 1024);
					}
					else
					{
						strncpy(aOption->StringValue, aOption->Files[SelectedFile].BaseName, 1024);
					}
					return 1;
				}
			}
			else if(e.type == SDL_QUIT)
			{
				return -1;
			}
		}
	}

	return 0;
}


/* Define input function mapping */
typedef int (*OPTIONS_ItemInputFn)(OptionDef* aOption, SDLKey aKey);
static OPTIONS_ItemInputFn OPTIONS_ItemInputs[] = {OPTIONS_NULLInput, OPTIONS_IntInput, OPTIONS_NULLInput, OPTIONS_StringInput, OPTIONS_IntInput, OPTIONS_FileListInput};

/* Run the options menu */
int					OPTIONS_Do					(int* argc, char*** argv, uint32_t aSelect)
{
	SDL_Event e;
	int32_t SelectedOption = 0;

	OPTIONS_Draw(Options[SelectedOption].Description, OPTIONS_GetString, SelectedOption);

	while(SDL_WaitEvent(&e))
	{
		if(e.type == SDL_KEYDOWN)
		{
			if(e.key.keysym.sym == SDLK_DOWN)
			{
				SelectedOption ++;
				SelectedOption = (Options[SelectedOption].Type == 0) ? SelectedOption - 1 : SelectedOption;
				OPTIONS_Draw(Options[SelectedOption].Description, OPTIONS_GetString, SelectedOption);
			}
			else if(e.key.keysym.sym == SDLK_UP)
			{
				SelectedOption --;
				SelectedOption = (SelectedOption < 0) ? 0 : SelectedOption;
				OPTIONS_Draw(Options[SelectedOption].Description, OPTIONS_GetString, SelectedOption);
			}
			else if(e.key.keysym.sym == SDLK_LEFT || e.key.keysym.sym == SDLK_RIGHT)
			{
				int32_t result = OPTIONS_ItemInputs[Options[SelectedOption].Type](&Options[SelectedOption], e.key.keysym.sym);
				if(result > 0)
				{
					OPTIONS_Draw(Options[SelectedOption].Description, OPTIONS_GetString, SelectedOption);
				}
				else if(result < 0)
				{
					return -1;
				}
			}
			else if(e.key.keysym.sym == SDLK_F12)
			{
				Options[SelectedOption].Active = !Options[SelectedOption].Active;
				OPTIONS_Draw(Options[SelectedOption].Description, OPTIONS_GetString, SelectedOption);
			}
			else if(e.key.keysym.sym == SDLK_RETURN)
			{
				return OPTIONS_Dont(argc, argv, aSelect);
			}				
		}
		else if(e.type == SDL_QUIT)
		{
			return -1;
		}
	}

	return -1;
}

int					OPTIONS_Dont				(int* argc, char*** argv, uint32_t aSelect)
{
	if(aSelect < FoundGames)
	{
		int havesavedir = !MakeSaveDir(Games[aSelect]->BaseName);
		int index = 0;

		AttachString("prboom");
		AttachString("-iwad");
		AttachString(Games[aSelect]->WAD);

		if(havesavedir)
		{
			char buffer[MAX_PATH];
			AttachString("-save");
			snprintf(buffer, MAX_PATH, SAVE_PATH"/%s", Games[aSelect]->BaseName);
			AttachString(buffer);
		}

		while(Options[index].Type)
		{
			if(Options[index].Active && Options[index].Type == 1)
			{
				AttachString(Options[index].ArgumentName);
				AttachInt(Options[index].IntValue);
			}
			else if(Options[index].Active && Options[index].Type == 2)
			{
				AttachString(Options[index].ArgumentName);
			}
			else if(Options[index].Active && Options[index].Type == 4)
			{
				AttachString(Options[index].ArgumentName);
				AttachString(Options[index].EnumValues[Options[index].IntValue].Value);
			}
			else if(Options[index].Active && Options[index].Type == 5)
			{
				char* token = strtok(Options[index].StringValue, " ");

				AttachString(Options[index].ArgumentName);

				while(token)
				{
					for(int i = 0; i != *(Options[index].FileCount); i ++)
					{
						if(strcmp(token, Options[index].Files[i].BaseName) == 0)
						{
							AttachString(Options[index].Files[i].WAD);
						}
					}

					token = strtok(0, " ");
				}
			}

			index ++;
		}

		*argc = ArgumentCount;
		*argv = Arguments;

		return 0;
	}

	return -1;
}

